PE结构体中导出表/导入表解析——初阶
一、导出表解析
输出表位置,落在了.rdata段,
16000【5200】
17D70【6F70】
从而,可以知道17D70,输出表在磁盘中的偏移是6F70
在010里,Ctrl + G,输入6F70
这里,先看下导出表的数据结构,40B,
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name; // DLL的名称地址
DWORD Base; // 索引基数
DWORD NumberOfFunctions; // 函数地址表大小
DWORD NumberOfNames; // 函数名表大小 == 函数序号表大小
DWORD AddressOfFunctions; // 函数地址表——首地址
DWORD AddressOfNames; // 函数名表——首地址
DWORD AddressOfNameOrdinals; // 函数序号表——首地址
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
那就从6F70的位置,开始,找40B,如下:
00 00 00 00
71 DB 67 5A 【时间戳】
00 00 【主版本】
00 00 【次版本】
C0 7D 01 00【DLL名称地址】
01 00 00 00 【索引基数】
04 00 00 00 【函数地址表大小】
04 00 00 00 【函数名表大小 == 函数序号表大小】
98 7D 01 00【函数地址表——首地址】
A8 7D 01 00 【函数名表——首地址】
B8 7D 01 00【函数序号表——首地址】
1、看DLL的名称是啥:地址17DC0【6FC0】,找到了我们自己的库dll_00.dll
2、再看下函数地址表中的元素,首地址17D98【6F98】,共有4个,地址,4B/个
如下所示:
3、再来看函数名表中的元素,首地址17DA8【6FA8】,共有4个,地址4B/个
这些都是地址值,要找到真正的函数名:
17DD0【6FD0】
17DD5【6FD5】
17DDA【6FDA】
17DCB【6FCB】
特别注意:函数名表,其实存放的也是地址值,RVA,这个只是我们自己找到的名称,方便起见,直接写的名字
4、接下来,看下函数序号表,首地址17DB8【6FB8】,4个,序号,2B/个
5、接下来,就分析分析:从这里,也可以看到,序号表里的值,并没有加上索引基数
最终,会得到如下结果:
6、验证下,我们的结果:成功了;
至于,索引基数,还没看到效果呢,————注意看下刚刚的LoadPe里的Ordinal那一列
部分代码:
#pragma once
#define WIN32DLL_EXPORTS
#ifdef __cplusplus
extern "C" {
#endif
#ifdef WIN32DLL_EXPORTS
#define WIN32DLL_API __declspec(dllexport)
#else
#define WIN32DLL_API __declspec(dllimport)
#endif
WIN32DLL_API void Fun1();
WIN32DLL_API void Fun2();
WIN32DLL_API void Fun3();
#ifdef __cplusplus
}
#endif
def文件
LIBRARY;
EXPORTS;
Fun4;
二、导入表解析
写一个测试程序,查看导入表RVA
输入表位置,落在了.idata段
1A000【7400】
1A1E8【75E8】
共20B
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk; // RVA to IAT
} IMAGE_IMPORT_DESCRIPTOR;
2C A3 01 00 【OriginalFirstThunk:INT(Import Name Table)导入名称表地址RVA】
00 00 00 00
00 00 00 00
54 A4 01 00【DLL名称(地址值)】
E0 A0 01 00【IAT(Import Address Table)导入地址表地址RVA】
1、首先看下DLL的名字,1A454【7854】
2、看下INT(OriginalFirstThunk):1A32C【772C】,全0结尾
函数名数组:
44 A4 01 00 ————1A444【7844】——————最高位为0,说明是名称导入的,不是序号导入的;
3C A4 01 00 ————1A43C【783C】——————
4C A4 01 00 ————1A44C【784C】——————
34 A4 01 00 ————1A434【7834】——————
注意:IAT和INT都指向下面的数据结构,4B
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD,导入函数的地址,在加载到内存后,这里才起作用
DWORD Ordinal; // 假如是序号导入的,会用到这里
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME,假如是函数名导入的,用到这里,它指向另外一个结构体:PIMGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
// 如果是函数名导入的,AddressOfData会指向下面这个结构体
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
由上可知,函数名导入的,因此,上面的地址值,就会指向一个PIMAGE_IMPORT_BY_NAME的结构体:
【7844】Fun3
【783C】Fun2
【784C】Fun4
【7834】Fun1
3、看下IAT,1A0E0【74E0】全0结束,IAT和INT一样,都指向IMAGE_THUNK_DATA32结构体,4B
可见,最高位都是0,所以,也是名称导入的,另外,还可以发现,这个位置的值,和INT的值是一样的,因此,不再赘述了;
44 A4 01 00
3C A4 01 00
4C A4 01 00
34 A4 01 00
#include <stdio.h>
extern "C" __declspec(dllimport) void Fun1();
extern "C" __declspec(dllimport) void Fun2();
extern "C" __declspec(dllimport) void Fun3();
// 如果是在def中导出的,需要如下声明
void Fun4();
#pragma comment(lib, "../Debug/dll_00.lib")
int main(int argc, char** argv) {
Fun1();
Fun2();
Fun3();
Fun4();
getchar();
return 0;
}
三、如果,修改def为
LIBRARY;
EXPORTS;
Fun4 @1;
看下导入表里的INT/IAT:
可见,这里的一项,最高位为1,序号导入,这个序号,就是dll export的那个序号
至此,PE结构中,导入/导出表的介绍结束;
PS:I Dare to do sth I feared,作为一枚奋斗青年,也是一枚小白,最近在学习PE结构相关的知识,这篇帖子也算是自己的一个总结;希望能对需要的人以帮助;也期待大神们的更多指导;
本文由看雪论坛 Reginald 原创
转载请注明来自看雪社区
热门阅读
点击阅读原文/read,
更多干货等着你~